// Hardware Buffer For OpenGL
// (c) jimon game studio

#include "CHardwareBuffer_OpenGL.h"

#ifdef JE_RENDER_OPENGL

namespace je
{
	namespace video
	{
		//! Constructor
		CHardwareBuffer_OpenGL::CHardwareBuffer_OpenGL(CRender_OpenGL_Extensions * NewExtensions,GLenum Target,Graphics3dDataType DataType,OpenGLWorkMode WorkMode)
			:Extensions(NewExtensions),Array(NULL),CurrentDataType(DataType),Locked(false),Size(0),Position(0),BindTarget(Target),VboId(0),CurrentFromHWtoSWwhileLock(false),CurrentFromSWtoHWwhileUnlock(true),CurrentWorkMode(WorkMode)
		{
		}

		//! Destructor
		CHardwareBuffer_OpenGL::~CHardwareBuffer_OpenGL()
		{
			if(Array)
			{
				Lock(core::BLT_READWRITE);
				DeAllocate();
				UnLock();
			}
		}

		//! Lock Buffer
		void CHardwareBuffer_OpenGL::Lock(core::BufferLockType NewLockType)
		{
			JEASSERT(!Locked)

			Locked = true;
			LockType = NewLockType;

			if((CurrentWorkMode!=OWM_11) && CurrentFromHWtoSWwhileLock)
				MoveFromHardwareToSoftware();
		}

		//! UnLock Buffer
		void CHardwareBuffer_OpenGL::UnLock()
		{
			JEASSERT(Locked)
			GenerateBuffers();
			Locked = false;
		}

		//! Read Data
		void CHardwareBuffer_OpenGL::Read(void * Pointer,u32 Bytes)
		{
			JEASSERT(Locked)
			JEASSERT((LockType == core::BLT_READ) || (LockType == core::BLT_READWRITE))

			#ifdef JE_MSVC
			memcpy_s(Pointer,Bytes,Array+Position,Bytes);
			#else
			memcpy(Pointer,Array+Position,Bytes);
			#endif

			if(StreamWorkMode)
				Position += Bytes;
		}

		//! Write Data
		void CHardwareBuffer_OpenGL::Write(const void * Pointer,u32 Bytes)
		{
			JEASSERT(Locked)
			JEASSERT((LockType == core::BLT_WRITE) || (LockType == core::BLT_READWRITE))

			#ifdef JE_MSVC
			memcpy_s(Array+Position,Size - Position,Pointer,Bytes);
			#else
			memcpy(Array+Position,Pointer,Bytes);
			#endif

			if(StreamWorkMode)
				Position += Bytes;
		}

		//! Write Data From Other Buffer
		void CHardwareBuffer_OpenGL::WriteFrom(core::IBuffer * Buffer,u32 Bytes)
		{
			Buffer->Read(Array + Position,Bytes);
		}

		//! Allocate Buffer for data
		void CHardwareBuffer_OpenGL::Allocate(u32 NewSize)
		{
			if(Array)
				Array = reinterpret_cast<c8*>(core::Realloc(Array,NewSize));
			else
				Array = reinterpret_cast<c8*>(core::Alloc(NewSize));
			Size = NewSize;
		}

		//! DeAllocate Buffer
		void CHardwareBuffer_OpenGL::DeAllocate()
		{
			JEASSERT(Array)
			core::Free(Array);
			Array = NULL;
			Size = 0;

			if(CurrentWorkMode!=OWM_11)
				Extensions->DeleteBuffers(1,&VboId);
			VboId = 0;
		}

		//! Merge Data
		void CHardwareBuffer_OpenGL::Merge(const void * Pointer,u32 ArraySize,u32 ObjectSize,u32 Stride)
		{
			JEASSERT(Locked)
			JEASSERT((LockType == core::BLT_WRITE) || (LockType == core::BLT_READWRITE))

			c8 * TempArray = reinterpret_cast<c8*>(core::Alloc(Size));

			s32 CurrentObjectSize = Stride-ObjectSize;

			if(CurrentObjectSize>0)
				for(u32 i=0;i<(ArraySize/ObjectSize);i++)
				{
					#ifdef JE_MSVC
					memcpy_s(TempArray+i*Stride,Size-i*Stride,Array+i*CurrentObjectSize,CurrentObjectSize);
					memcpy_s(TempArray+i*Stride+CurrentObjectSize,Size-(i*Stride+CurrentObjectSize),(c8*)Pointer+i*ObjectSize,ObjectSize);
					#else
					memcpy(TempArray+i*Stride,Array+i*CurrentObjectSize,CurrentObjectSize);
					memcpy(TempArray+i*Stride+CurrentObjectSize,(c8*)Pointer+i*ObjectSize,ObjectSize);
					#endif
				}
			else
				#ifdef JE_MSVC
				memcpy_s(TempArray,Size,Pointer,ArraySize);
				#else
				memcpy(TempArray,Pointer,ArraySize);
				#endif

			core::Free(Array);
			Array = TempArray;
		}

		//! Get merged data blocks
		void CHardwareBuffer_OpenGL::UnMerge(void * Pointer,u32 ArraySize,u32 ObjectSize,u32 Stride,u32 Offset)
		{
			JEASSERT(Locked)
			JEASSERT((LockType == core::BLT_READ) || (LockType == core::BLT_READWRITE))

			s32 CurrentObjectSize = Stride-ObjectSize;

			if(CurrentObjectSize>0)
				for(u32 i=0;i<(ArraySize/ObjectSize);i++)
					#ifdef JE_MSVC
					memcpy_s((c8*)Pointer+i*ObjectSize,ObjectSize,Array+i*Stride+Offset,ObjectSize);
					#else
					memcpy((c8*)Pointer+i*ObjectSize,Array+i*Stride+Offset,ObjectSize);
					#endif
			else
				#ifdef JE_MSVC
				memcpy_s(Pointer,Size,Array,ArraySize);
				#else
				memcpy(Pointer,Array,ArraySize);
				#endif
		}

		//! move data from software buffer to hardware buffer
		void CHardwareBuffer_OpenGL::MoveFromSoftwareToHardware()
		{
			JEASSERT(Locked)

			if(CurrentWorkMode!=OWM_11)
			{
				Extensions->BindBuffer(BindTarget,VboId);
				Extensions->BufferData(BindTarget,Size,Array,GL_DYNAMIC_DRAW);
				if(CurrentWorkMode != OWM_30)
					Extensions->BindBuffer(BindTarget,NULL);
			}
		}

		//! move data from hardware buffer to software buffer
		void CHardwareBuffer_OpenGL::MoveFromHardwareToSoftware()
		{
			JEASSERT(Locked)

			if(CurrentWorkMode!=OWM_11)
			{
				Extensions->BindBuffer(BindTarget,VboId);

				void * TempArray = Extensions->MapBuffer(BindTarget,GL_READ_ONLY);
				#ifdef JE_MSVC
				memcpy_s(Array,Size,TempArray,Size);
				#else
				memcpy(Array,TempArray,Size);
				#endif

				Extensions->UnmapBuffer(BindTarget);
				if(CurrentWorkMode != OWM_30)
					Extensions->BindBuffer(BindTarget,NULL);
			}
		}

		//! Copy Buffer to a new context or something else
		IHardwareBuffer * CHardwareBuffer_OpenGL::CopyAndRecreateBuffer()
		{
			JEASSERT(Locked)

			CHardwareBuffer_OpenGL * NewBuffer;
			JENEW(NewBuffer,CHardwareBuffer_OpenGL(Extensions,BindTarget,CurrentDataType,CurrentWorkMode))

			NewBuffer->Lock(core::BLT_WRITE);
			NewBuffer->Allocate(Size);
			NewBuffer->Write(Array,Size);
			NewBuffer->UnLock();

			NewBuffer->BufferAutoUpdateOptions(CurrentFromHWtoSWwhileLock,CurrentFromSWtoHWwhileUnlock);

			return NewBuffer;
		}

		//! Generate Buffers
		void CHardwareBuffer_OpenGL::GenerateBuffers()
		{
			if((CurrentWorkMode!=OWM_11) && Size && Array)
			{
				if(!VboId)
					Extensions->GenerateBuffer(1,&VboId);

				if(CurrentFromSWtoHWwhileUnlock)
					MoveFromSoftwareToHardware();
			}
		}


	}
}

#endif
